package PduParser;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.HashMap;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PduParser {
	private static final Logger logger = LoggerFactory.getLogger(PduParser.class);
	/**
	 * The next are WAP values defined in WSP specification.
	 */
	private static final int QUOTE = 127;
	private static final int LENGTH_QUOTE = 31;
	private static final int TEXT_MIN = 32;
	private static final int TEXT_MAX = 127;
	private static final int SHORT_INTEGER_MAX = 127;
	private static final int SHORT_LENGTH_MAX = 30;
	private static final int LONG_INTEGER_LENGTH_MAX = 8;
	private static final int QUOTED_STRING_FLAG = 34;
	private static final int END_STRING_FLAG = 0x00;
	// The next two are used by the interface "parseWapString" to
	// distinguish Text-String and Quoted-String.
	private static final int TYPE_TEXT_STRING = 0;
	private static final int TYPE_QUOTED_STRING = 1;
	private static final int TYPE_TOKEN_STRING = 2;

	/**
	 * Specify the part position.
	 */
	private static final int THE_FIRST_PART = 0;
	private static final int THE_LAST_PART = 1;

	/**
	 * The pdu data.
	 */
	private ByteArrayInputStream mPduDataStream = null;

	/**
	 * Store pdu headers
	 */
	private PduHeaders mHeaders = null;

	/**
	 * Store pdu parts.
	 */
	private PduBody mBody = null;

	/**
	 * Store the "type" parameter in "Content-Type" header field.
	 */
	private static byte[] mTypeParam = null;

	/**
	 * Store the "start" parameter in "Content-Type" header field.
	 */
	private static byte[] mStartParam = null;

	/**
	 * The log tag.
	 */
	private static final String LOG_TAG = "PduParser";
	private static final boolean DEBUG = false;
    /// M: For fix bug, parse read report failed.
    private static final int UNSIGNED_INT_LIMIT = 2;
    /**
	 * Log status.
	 *
	 * @param text
	 *            log information
	 */
	private static void log(String text) {
		logger.warn(text);
	}
	 
	/**
	 * Constructor.
	 *
	 * @param pduDataStream
	 *            pdu data to be parsed
	 */
	public PduParser(byte[] pduDataStream) {
		mPduDataStream = new ByteArrayInputStream(pduDataStream);
	}

	/**
	 * Parse the pdu.
	 *
	 * @return the pdu structure if parsing successfully. null if parsing error
	 *         happened or mandatory fields are not set.
	 */
	public GenericPdu parse() {
		if (mPduDataStream == null) {
			return null;
		}

		/* parse headers */
		mHeaders = parseHeaders(mPduDataStream);
		if (null == mHeaders) {
			// Parse headers failed.
			return null;
		}

		/* get the message type */
		int messageType = mHeaders.getOctet(PduHeaders.MESSAGE_TYPE);

		/* check mandatory header fields */
		if (false == checkMandatoryHeader(mHeaders)) {
			logger.warn("check mandatory headers failed!");
			return null;
		}
		
        /// M:Code analyze 003,parse mPduDataStream,if the unsigned integer is more than 2
        /// reconstruct retrieveConf @{
        mPduDataStream.mark(1);
        int count = parseUnsignedInt(mPduDataStream);
        mPduDataStream.reset();
        if (PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType && count >= UNSIGNED_INT_LIMIT) {
            byte[] contentType = mHeaders.getTextString(PduHeaders.CONTENT_TYPE);
            if (null == contentType) {
            	logger.warn("Parse MESSAGE_TYPE_RETRIEVE_CONF Failed: content Type is null _0");
                return null;
            }
            String contentTypeStr = new String(contentType);
            contentTypeStr = contentTypeStr.toLowerCase();
            if (!contentTypeStr.equals(ContentType.MULTIPART_MIXED)
                    && !contentTypeStr.equals(ContentType.MULTIPART_RELATED)
                    && !contentTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) {

                if (contentTypeStr.equals(ContentType.TEXT_PLAIN)) {
                    logger.debug( "Content Type is text/plain");

                    PduPart theOnlyPart = new PduPart();
                    theOnlyPart.setContentType(contentType);
                    theOnlyPart.setContentLocation(Long.toOctalString(
                            System.currentTimeMillis()).getBytes());
                    theOnlyPart.setContentId("<part1>".getBytes());

                    mPduDataStream.mark(1);

                    int partDataLen = 0;
                    while (mPduDataStream.read() != -1) {
                        partDataLen++;
                    }

                    byte[] partData = new byte[partDataLen];
                    logger.debug( "got part length: " + partDataLen);
                    mPduDataStream.reset();
                    mPduDataStream.read(partData, 0, partDataLen);
                    String showData = new String(partData);
                    logger.debug( "show data: " + showData);

                    theOnlyPart.setData(partData);
                    logger.debug( "setData finish");
                    PduBody onlyPartBody = new PduBody();
                    onlyPartBody.addPart(theOnlyPart);
                    RetrieveConf retrieveConf = null;
                    try {
                        retrieveConf = new RetrieveConf(mHeaders, onlyPartBody);
                    } catch (Exception e) {
                        logger.warn( "new RetrieveConf has exception");
                    }
                    if (retrieveConf == null) {
                        logger.warn( "retrieveConf is null");
                    }
                    return retrieveConf;
                }

                // Here maybe we can add other content type support.
            }
        }

		if ((PduHeaders.MESSAGE_TYPE_SEND_REQ == messageType) || (PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType)) {
			/* need to parse the parts */
			mBody = parseParts(mPduDataStream);
			if (null == mBody) {
				// Parse parts failed.
				return null;
			}
		}

	      switch (messageType) {
          case PduHeaders.MESSAGE_TYPE_SEND_REQ:
            
        	  logger.debug( "parse: MESSAGE_TYPE_SEND_REQ");
              
              SendReq sendReq = new SendReq(mHeaders, mBody);
              return sendReq;
          case PduHeaders.MESSAGE_TYPE_SEND_CONF:
                  logger.debug( "parse: MESSAGE_TYPE_SEND_CONF");
              SendConf sendConf = new SendConf(mHeaders);
              return sendConf;
          case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
                  logger.debug( "parse: MESSAGE_TYPE_NOTIFICATION_IND");
              NotificationInd notificationInd =
                  new NotificationInd(mHeaders);
              return notificationInd;
          case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
                  logger.debug( "parse: MESSAGE_TYPE_NOTIFYRESP_IND");
              NotifyRespInd notifyRespInd =
                  new NotifyRespInd(mHeaders);
              return notifyRespInd;
          case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
                  logger.debug( "parse: MESSAGE_TYPE_RETRIEVE_CONF");
              RetrieveConf retrieveConf =
                  new RetrieveConf(mHeaders, mBody);

              byte[] contentType = retrieveConf.getContentType();
              if (null == contentType) {
                  logger.warn( "Parse MESSAGE_TYPE_RETRIEVE_CONF Failed: content Type is null _1");
                  return null;
              }
              String ctTypeStr = new String(contentType);
              /// M:Code analyze 004,make the string into lowercase,matching the content type @{
              ctTypeStr = ctTypeStr.toLowerCase();
              /// @}
              if (ctTypeStr.equals(ContentType.MULTIPART_MIXED)
                      || ctTypeStr.equals(ContentType.MULTIPART_RELATED)
                      || ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)
                      /// M:Code analyze 005,add a new content type text/plain
                      || ctTypeStr.equals(ContentType.TEXT_PLAIN)) {
                  // The MMS content type must be "application/vnd.wap.multipart.mixed"
                  // or "application/vnd.wap.multipart.related"
                  // or "application/vnd.wap.multipart.alternative"
                  return retrieveConf;
              } else if (ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) {
                  // "application/vnd.wap.multipart.alternative"
                  // should take only the first part.
                  PduPart firstPart = mBody.getPart(0);
                  mBody.removeAll();
                  mBody.addPart(0, firstPart);
                  return retrieveConf;
              }
              logger.warn( "Parse MESSAGE_TYPE_RETRIEVE_CONF Failed: content Type is null _2");
              return null;
          case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
                  logger.debug( "parse: MESSAGE_TYPE_DELIVERY_IND");
              DeliveryInd deliveryInd =
                  new DeliveryInd(mHeaders);
              return deliveryInd;
          case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
                  logger.debug( "parse: MESSAGE_TYPE_ACKNOWLEDGE_IND");
              AcknowledgeInd acknowledgeInd =
                  new AcknowledgeInd(mHeaders);
              return acknowledgeInd;
          case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
                  logger.debug( "parse: MESSAGE_TYPE_READ_ORIG_IND");
              ReadOrigInd readOrigInd =
                  new ReadOrigInd(mHeaders);
              return readOrigInd;
          case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
                  logger.debug( "parse: MESSAGE_TYPE_READ_REC_IND");
              ReadRecInd readRecInd =
                  new ReadRecInd(mHeaders);
              return readRecInd;
          default:
        	  logger.warn("Parser doesn't support this message type in this version!");
          return null;
      }
	}

	/**
	 * Parse pdu headers.
	 *
	 * @param pduDataStream
	 *            pdu data input stream
	 * @return headers in PduHeaders structure, null when parse fail
	 */
	protected PduHeaders parseHeaders(ByteArrayInputStream pduDataStream) {
		if (pduDataStream == null) {
			return null;
		}

		boolean keepParsing = true;
		PduHeaders headers = new PduHeaders();

		while (keepParsing && (pduDataStream.available() > 0)) {
			pduDataStream.mark(1);
			int headerField = extractByteValue(pduDataStream);
			/* parse custom text header */
			if ((headerField >= TEXT_MIN) && (headerField <= TEXT_MAX)) {
				pduDataStream.reset();
				byte[] bVal = parseWapString(pduDataStream, TYPE_TEXT_STRING);
				/* we should ignore it at the moment */
				continue;
			}
			switch (headerField) {
			case PduHeaders.MESSAGE_TYPE: {
				int messageType = extractByteValue(pduDataStream);
				switch (messageType) {
				// We don't support these kind of messages now.
				case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
				case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
				case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
				case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
				case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
				case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
				case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
				case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
				case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
				case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
				case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
				case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
				case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
				case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
				case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
					return null;
				}
				try {
					headers.setOctet(messageType, headerField);
				} catch (InvalidHeaderValueException e) {
					log("Set invalid Octet value: " + messageType + " into the header filed: " + headerField);
					return null;
				} catch (RuntimeException e) {
					log(headerField + "is not Octet header field!");
					return null;
				}
				break;
			}
			/* Octect value */
			case PduHeaders.REPORT_ALLOWED:
			case PduHeaders.ADAPTATION_ALLOWED:
			case PduHeaders.DELIVERY_REPORT:
			case PduHeaders.DRM_CONTENT:
			case PduHeaders.DISTRIBUTION_INDICATOR:
			case PduHeaders.QUOTAS:
			case PduHeaders.READ_REPORT:
			case PduHeaders.STORE:
			case PduHeaders.STORED:
			case PduHeaders.TOTALS:
			case PduHeaders.SENDER_VISIBILITY:
			case PduHeaders.READ_STATUS:
			case PduHeaders.CANCEL_STATUS:
			case PduHeaders.PRIORITY:
			case PduHeaders.STATUS:
			case PduHeaders.REPLY_CHARGING:
			case PduHeaders.MM_STATE:
			case PduHeaders.RECOMMENDED_RETRIEVAL_MODE:
			case PduHeaders.CONTENT_CLASS:
			case PduHeaders.RETRIEVE_STATUS:
			case PduHeaders.STORE_STATUS:
				/**
				 * The following field has a different value when used in the
				 * M-Mbox-Delete.conf and M-Delete.conf PDU. For now we ignore
				 * this fact, since we do not support these PDUs
				 */
			case PduHeaders.RESPONSE_STATUS: {
				int value = extractByteValue(pduDataStream);

				try {
					headers.setOctet(value, headerField);
				} catch (InvalidHeaderValueException e) {
					log("Set invalid Octet value: " + value + " into the header filed: " + headerField);
					return null;
				} catch (RuntimeException e) {
					log(headerField + "is not Octet header field!");
					return null;
				}
				break;
			}

			/* Long-Integer */
			case PduHeaders.DATE:
			case PduHeaders.REPLY_CHARGING_SIZE:
			case PduHeaders.MESSAGE_SIZE: {
				try {
					long value = parseLongInteger(pduDataStream);
					headers.setLongInteger(value, headerField);
				} catch (RuntimeException e) {
					log(headerField + "is not Long-Integer header field!");
					return null;
				}
				break;
			}

			/* Integer-Value */
			case PduHeaders.MESSAGE_COUNT:
			case PduHeaders.START:
			case PduHeaders.LIMIT: {
				try {
					long value = parseIntegerValue(pduDataStream);
					headers.setLongInteger(value, headerField);
				} catch (RuntimeException e) {
					log(headerField + "is not Long-Integer header field!");
					return null;
				}
				break;
			}

			/* Text-String */
			case PduHeaders.TRANSACTION_ID:
			case PduHeaders.REPLY_CHARGING_ID:
			case PduHeaders.AUX_APPLIC_ID:
			case PduHeaders.APPLIC_ID:
			case PduHeaders.REPLY_APPLIC_ID:
				/**
				 * The next three header fields are email addresses as defined
				 * in RFC2822, not including the characters "<" and ">"
				 */
			case PduHeaders.MESSAGE_ID:
			case PduHeaders.REPLACE_ID:
			case PduHeaders.CANCEL_ID:
				/**
				 * The following field has a different value when used in the
				 * M-Mbox-Delete.conf and M-Delete.conf PDU. For now we ignore
				 * this fact, since we do not support these PDUs
				 */
			case PduHeaders.CONTENT_LOCATION: {
				byte[] value = parseWapString(pduDataStream, TYPE_TEXT_STRING);
				if (null != value) {
					try {
						headers.setTextString(value, headerField);
					} catch (NullPointerException e) {
						log("null pointer error!");
					} catch (RuntimeException e) {
						log(headerField + "is not Text-String header field!");
						return null;
					}
				}
				break;
			}

			/* Encoded-string-value */
			case PduHeaders.SUBJECT:
			case PduHeaders.RECOMMENDED_RETRIEVAL_MODE_TEXT:
			case PduHeaders.RETRIEVE_TEXT:
			case PduHeaders.STATUS_TEXT:
			case PduHeaders.STORE_STATUS_TEXT:
				/*
				 * the next one is not support M-Mbox-Delete.conf and
				 * M-Delete.conf now
				 */
			case PduHeaders.RESPONSE_TEXT: {
				EncodedStringValue value = parseEncodedStringValue(pduDataStream);
				if (null != value) {
					try {
						headers.setEncodedStringValue(value, headerField);
					} catch (NullPointerException e) {
						log("null pointer error!");
					} catch (RuntimeException e) {
						log(headerField + "is not Encoded-String-Value header field!");
						return null;
					}
				}
				break;
			}

			/* Addressing model */
			case PduHeaders.BCC:
			case PduHeaders.CC:
			case PduHeaders.TO: {
				EncodedStringValue value = parseEncodedStringValue(pduDataStream);
				if (null != value) {
					byte[] address = value.getTextString();
					if (null != address) {
						String str = new String(address);
						int endIndex = str.indexOf("/");
						if (endIndex > 0) {
							str = str.substring(0, endIndex);
						}
						try {
							value.setTextString(str.getBytes());
						} catch (NullPointerException e) {
							log("null pointer error!");
							return null;
						}
					}

					try {
						headers.appendEncodedStringValue(value, headerField);
					} catch (NullPointerException e) {
						log("null pointer error!");
					} catch (RuntimeException e) {
						log(headerField + "is not Encoded-String-Value header field!");
						return null;
					}
				}
				break;
			}

			/*
			 * Value-length (Absolute-token Date-value | Relative-token
			 * Delta-seconds-value)
			 */
			case PduHeaders.DELIVERY_TIME:
			case PduHeaders.EXPIRY:
			case PduHeaders.REPLY_CHARGING_DEADLINE: {
				/* parse Value-length */
				parseValueLength(pduDataStream);

				/* Absolute-token or Relative-token */
				int token = extractByteValue(pduDataStream);

				/* Date-value or Delta-seconds-value */
				long timeValue;
				try {
					timeValue = parseLongInteger(pduDataStream);
				} catch (RuntimeException e) {
					log(headerField + "is not Long-Integer header field!");
					return null;
				}
				if (PduHeaders.VALUE_RELATIVE_TOKEN == token) {
					/*
					 * need to convert the Delta-seconds-value into Date-value
					 */
					timeValue = System.currentTimeMillis() / 1000 + timeValue;
				}

				try {
					headers.setLongInteger(timeValue, headerField);
				} catch (RuntimeException e) {
					log(headerField + "is not Long-Integer header field!");
					return null;
				}
				break;
			}

			case PduHeaders.FROM: {
				/*
				 * From-value = Value-length (Address-present-token
				 * Encoded-string-value | Insert-address-token)
				 */
				EncodedStringValue from = null;
				parseValueLength(pduDataStream); /* parse value-length */

				/* Address-present-token or Insert-address-token */
				int fromToken = extractByteValue(pduDataStream);

				/* Address-present-token or Insert-address-token */
				if (PduHeaders.FROM_ADDRESS_PRESENT_TOKEN == fromToken) {
					/* Encoded-string-value */
					from = parseEncodedStringValue(pduDataStream);
					if (null != from) {
						byte[] address = from.getTextString();
						if (null != address) {
							String str = new String(address);
							int endIndex = str.indexOf("/");
							if (endIndex > 0) {
								str = str.substring(0, endIndex);
							}
							try {
								from.setTextString(str.getBytes());
							} catch (NullPointerException e) {
								log("null pointer error!");
								return null;
							}
						}
					}
				} else {
					try {
						from = new EncodedStringValue(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes());
					} catch (NullPointerException e) {
						log(headerField + "is not Encoded-String-Value header field!");
						return null;
					}
				}

				try {
					headers.setEncodedStringValue(from, PduHeaders.FROM);
				} catch (NullPointerException e) {
					log("null pointer error!");
				} catch (RuntimeException e) {
					log(headerField + "is not Encoded-String-Value header field!");
					return null;
				}
				break;
			}

			case PduHeaders.MESSAGE_CLASS: {
				/* Message-class-value = Class-identifier | Token-text */
				pduDataStream.mark(1);
				int messageClass = extractByteValue(pduDataStream);

				if (messageClass >= PduHeaders.MESSAGE_CLASS_PERSONAL) {
					/* Class-identifier */
					try {
						if (PduHeaders.MESSAGE_CLASS_PERSONAL == messageClass) {
							headers.setTextString(PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes(), PduHeaders.MESSAGE_CLASS);
						} else if (PduHeaders.MESSAGE_CLASS_ADVERTISEMENT == messageClass) {
							headers.setTextString(PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes(), PduHeaders.MESSAGE_CLASS);
						} else if (PduHeaders.MESSAGE_CLASS_INFORMATIONAL == messageClass) {
							headers.setTextString(PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes(), PduHeaders.MESSAGE_CLASS);
						} else if (PduHeaders.MESSAGE_CLASS_AUTO == messageClass) {
							headers.setTextString(PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes(), PduHeaders.MESSAGE_CLASS);
						}
					} catch (NullPointerException e) {
						log("null pointer error!");
					} catch (RuntimeException e) {
						log(headerField + "is not Text-String header field!");
						return null;
					}
				} else {
					/* Token-text */
					pduDataStream.reset();
					byte[] messageClassString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
					if (null != messageClassString) {
						try {
							headers.setTextString(messageClassString, PduHeaders.MESSAGE_CLASS);
						} catch (NullPointerException e) {
							log("null pointer error!");
						} catch (RuntimeException e) {
							log(headerField + "is not Text-String header field!");
							return null;
						}
					}
				}
				break;
			}

			case PduHeaders.MMS_VERSION: {
				int version = parseShortInteger(pduDataStream);

				try {
					headers.setOctet(version, PduHeaders.MMS_VERSION);
				} catch (InvalidHeaderValueException e) {
					log("Set invalid Octet value: " + version + " into the header filed: " + headerField);
					return null;
				} catch (RuntimeException e) {
					log(headerField + "is not Octet header field!");
					return null;
				}
				break;
			}

			case PduHeaders.PREVIOUSLY_SENT_BY: {
				/*
				 * Previously-sent-by-value = Value-length Forwarded-count-value
				 * Encoded-string-value
				 */
				/* parse value-length */
				parseValueLength(pduDataStream);

				/* parse Forwarded-count-value */
				try {
					parseIntegerValue(pduDataStream);
				} catch (RuntimeException e) {
					log(headerField + " is not Integer-Value");
					return null;
				}

				/* parse Encoded-string-value */
				EncodedStringValue previouslySentBy = parseEncodedStringValue(pduDataStream);
				if (null != previouslySentBy) {
					try {
						headers.setEncodedStringValue(previouslySentBy, PduHeaders.PREVIOUSLY_SENT_BY);
					} catch (NullPointerException e) {
						log("null pointer error!");
					} catch (RuntimeException e) {
						log(headerField + "is not Encoded-String-Value header field!");
						return null;
					}
				}
				break;
			}

			case PduHeaders.PREVIOUSLY_SENT_DATE: {
				/*
				 * Previously-sent-date-value = Value-length
				 * Forwarded-count-value Date-value
				 */
				/* parse value-length */
				parseValueLength(pduDataStream);

				/* parse Forwarded-count-value */
				try {
					parseIntegerValue(pduDataStream);
				} catch (RuntimeException e) {
					log(headerField + " is not Integer-Value");
					return null;
				}

				/* Date-value */
				try {
					long perviouslySentDate = parseLongInteger(pduDataStream);
					headers.setLongInteger(perviouslySentDate, PduHeaders.PREVIOUSLY_SENT_DATE);
				} catch (RuntimeException e) {
					log(headerField + "is not Long-Integer header field!");
					return null;
				}
				break;
			}

			case PduHeaders.MM_FLAGS: {
				/*
				 * MM-flags-value = Value-length ( Add-token | Remove-token |
				 * Filter-token ) Encoded-string-value
				 */

				/* parse Value-length */
				parseValueLength(pduDataStream);

				/* Add-token | Remove-token | Filter-token */
				extractByteValue(pduDataStream);

				/* Encoded-string-value */
				parseEncodedStringValue(pduDataStream);

				/*
				 * not store this header filed in "headers", because now
				 * PduHeaders doesn't support it
				 */
				break;
			}

			/*
			 * Value-length (Message-total-token | Size-total-token)
			 * Integer-Value
			 */
			case PduHeaders.MBOX_TOTALS:
			case PduHeaders.MBOX_QUOTAS: {
				/* Value-length */
				parseValueLength(pduDataStream);

				/* Message-total-token | Size-total-token */
				extractByteValue(pduDataStream);

				/* Integer-Value */
				try {
					parseIntegerValue(pduDataStream);
				} catch (RuntimeException e) {
					log(headerField + " is not Integer-Value");
					return null;
				}

				/*
				 * not store these headers filed in "headers", because now
				 * PduHeaders doesn't support them
				 */
				break;
			}

			case PduHeaders.ELEMENT_DESCRIPTOR: {
				parseContentType(pduDataStream, null);

				/*
				 * not store this header filed in "headers", because now
				 * PduHeaders doesn't support it
				 */
				break;
			}

			case PduHeaders.CONTENT_TYPE: {
				HashMap<Integer, Object> map = new HashMap<Integer, Object>();
				byte[] contentType = parseContentType(pduDataStream, map);

				if (null != contentType) {
					try {
						headers.setTextString(contentType, PduHeaders.CONTENT_TYPE);
					} catch (NullPointerException e) {
						log("null pointer error!");
					} catch (RuntimeException e) {
						log(headerField + "is not Text-String header field!");
						return null;
					}
				}

				/* get start parameter */
				mStartParam = (byte[]) map.get(PduPart.P_START);

				/* get charset parameter */
				mTypeParam = (byte[]) map.get(PduPart.P_TYPE);

				keepParsing = false;
				break;
			}

			case PduHeaders.CONTENT:
			case PduHeaders.ADDITIONAL_HEADERS:
			case PduHeaders.ATTRIBUTES:
			default: {
				log("Unknown header");
			}
			}
		}

		return headers;
	}

	/**
	 * Parse pdu parts.
	 *
	 * @param pduDataStream
	 *            pdu data input stream
	 * @return parts in PduBody structure
	 */
	protected static PduBody parseParts(ByteArrayInputStream pduDataStream) {
		if (pduDataStream == null) {
			return null;
		}

		int count = parseUnsignedInt(pduDataStream); // get the number of parts
		PduBody body = new PduBody();

		for (int i = 0; i < count; i++) {
			int headerLength = parseUnsignedInt(pduDataStream);
			int dataLength = parseUnsignedInt(pduDataStream);
			PduPart part = new PduPart();
			int startPos = pduDataStream.available();
			if (startPos <= 0) {
				// Invalid part.
				return null;
			}

			/* parse part's content-type */
			HashMap<Integer, Object> map = new HashMap<Integer, Object>();
			byte[] contentType = parseContentType(pduDataStream, map);
			if (null != contentType) {
				part.setContentType(contentType);
			} else {
				part.setContentType((PduContentTypes.contentTypes[0]).getBytes()); // "*/*"
			}

			/* get name parameter */
			byte[] name = (byte[]) map.get(PduPart.P_NAME);
			if (null != name) {
				part.setName(name);
			}

			/* get charset parameter */
			Integer charset = (Integer) map.get(PduPart.P_CHARSET);
			if (null != charset) {
				part.setCharset(charset);
			}

			/* parse part's headers */
			int endPos = pduDataStream.available();
			int partHeaderLen = headerLength - (startPos - endPos);
			if (partHeaderLen > 0) {
				if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) {
					// Parse part header faild.
					return null;
				}
			} else if (partHeaderLen < 0) {
				// Invalid length of content-type.
				return null;
			}

			/*
			 * FIXME: check content-id, name, filename and content location, if
			 * not set anyone of them, generate a default content-location
			 */
			if ((null == part.getContentLocation()) && (null == part.getName()) && (null == part.getFilename()) && (null == part.getContentId())) {
				part.setContentLocation(Long.toOctalString(System.currentTimeMillis()).getBytes());
			}

			/* get part's data */
			if (dataLength > 0) {
				byte[] partData = new byte[dataLength];
				String partContentType = new String(part.getContentType());
				int reallength = pduDataStream.read(partData, 0, dataLength);
				
				if (reallength < dataLength){
					byte[]  tmp = new byte[reallength];
					System.arraycopy(partData, 0, tmp, 0, reallength);
					partData = tmp;
				}
				
				if (partContentType.equalsIgnoreCase(ContentType.MULTIPART_ALTERNATIVE)) {
					// parse "multipart/vnd.wap.multipart.alternative".
					PduBody childBody = parseParts(new ByteArrayInputStream(partData));
					// take the first part of children.
					part = childBody.getPart(0);
				} else {
					// Check Content-Transfer-Encoding.
					byte[] partDataEncoding = part.getContentTransferEncoding();
					if (null != partDataEncoding) {
						String encoding = new String(partDataEncoding);
						if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) {
							// Decode "base64" into "binary".
							partData = Base64.decodeBase64(partData);
						} else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) {
							// Decode "quoted-printable" into "binary".
							partData = QuotedPrintable.decodeQuotedPrintable(partData);
						} else {
							// "binary" is the default encoding.
						}
					}
					if (null == partData) {
						logger.warn("Decode part data error!");
						return null;
					}
					part.setData(partData);
				}
			}

			/* add this part to body */
			if (THE_FIRST_PART == checkPartPosition(part)) {
				/* this is the first part */
				body.addPart(0, part);
			} else {
				/* add the part to the end */
				body.addPart(part);
			}
		}

		return body;
	}

	/**
	 * Parse unsigned integer.
	 *
	 * @param pduDataStream
	 *            pdu data input stream
	 * @return the integer, -1 when failed
	 */
	protected static int parseUnsignedInt(ByteArrayInputStream pduDataStream) {
		/**
		 * From wap-230-wsp-20010705-a.pdf The maximum size of a uintvar is 32
		 * bits. So it will be encoded in no more than 5 octets.
		 */
		assert (null != pduDataStream);
		int result = 0;
		int temp = pduDataStream.read();
		if (temp == -1) {
			return temp;
		}

		while ((temp & 0x80) != 0) {
			result = result << 7;
			result |= temp & 0x7F;
			temp = pduDataStream.read();
			if (temp == -1) {
				return temp;
			}
		}

		result = result << 7;
		result |= temp & 0x7F;

		return result;
	}

	/**
	 * Parse value length.
	 *
	 * @param pduDataStream
	 *            pdu data input stream
	 * @return the integer
	 */
	protected static int parseValueLength(ByteArrayInputStream pduDataStream) {
		/**
		 * From wap-230-wsp-20010705-a.pdf Value-length = Short-length |
		 * (Length-quote Length) Short-length = <Any octet 0-30> Length-quote =
		 * <Octet 31> Length = Uintvar-integer Uintvar-integer = 1*5 OCTET
		 */
		assert (null != pduDataStream);
		int temp = pduDataStream.read();
		assert (-1 != temp);
		int first = temp & 0xFF;

		if (first <= SHORT_LENGTH_MAX) {
			return first;
		} else if (first == LENGTH_QUOTE) {
			return parseUnsignedInt(pduDataStream);
		}

		throw new RuntimeException("Value length > LENGTH_QUOTE!");
	}

	/**
	 * Parse encoded string value.
	 *
	 * @param pduDataStream
	 *            pdu data input stream
	 * @return the EncodedStringValue
	 */
	protected static EncodedStringValue parseEncodedStringValue(ByteArrayInputStream pduDataStream) {
		/**
		 * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf Encoded-string-value =
		 * Text-string | Value-length Char-set Text-string
		 */
		assert (null != pduDataStream);
		pduDataStream.mark(1);
		EncodedStringValue returnValue = null;
		int charset = 0;
		int temp = pduDataStream.read();
		assert (-1 != temp);
		int first = temp & 0xFF;

		pduDataStream.reset();
		if (first < TEXT_MIN) {
			parseValueLength(pduDataStream);

			charset = parseShortInteger(pduDataStream); // get the "Charset"
		}

		byte[] textString = parseWapString(pduDataStream, TYPE_TEXT_STRING);

		try {
			if (0 != charset) {
				returnValue = new EncodedStringValue(charset, textString);
			} else {
				returnValue = new EncodedStringValue(textString);
			}
		} catch (Exception e) {
			return null;
		}

		return returnValue;
	}

	/**
	 * Parse Text-String or Quoted-String.
	 *
	 * @param pduDataStream
	 *            pdu data input stream
	 * @param stringType
	 *            TYPE_TEXT_STRING or TYPE_QUOTED_STRING
	 * @return the string without End-of-string in byte array
	 */
	protected static byte[] parseWapString(ByteArrayInputStream pduDataStream, int stringType) {
		assert (null != pduDataStream);
		/**
		 * From wap-230-wsp-20010705-a.pdf Text-string = [Quote] *TEXT
		 * End-of-string If the first character in the TEXT is in the range of
		 * 128-255, a Quote character must precede it. Otherwise the Quote
		 * character must be omitted. The Quote is not part of the contents.
		 * Quote = <Octet 127> End-of-string = <Octet 0>
		 *
		 * Quoted-string = <Octet 34> *TEXT End-of-string
		 *
		 * Token-text = Token End-of-string
		 */

		// Mark supposed beginning of Text-string
		// We will have to mark again if first char is QUOTE or
		// QUOTED_STRING_FLAG
		pduDataStream.mark(1);

		// Check first char
		int temp = pduDataStream.read();
		assert (-1 != temp);
		if ((TYPE_QUOTED_STRING == stringType) && (QUOTED_STRING_FLAG == temp)) {
			// Mark again if QUOTED_STRING_FLAG and ignore it
			pduDataStream.mark(1);
		} else if ((TYPE_TEXT_STRING == stringType) && (QUOTE == temp)) {
			// Mark again if QUOTE and ignore it
			pduDataStream.mark(1);
		} else {
			// Otherwise go back to origin
			pduDataStream.reset();
		}

		// We are now definitely at the beginning of string
		/**
		 * Return *TOKEN or *TEXT (Text-String without QUOTE, Quoted-String
		 * without QUOTED_STRING_FLAG and without End-of-string)
		 */
		return getWapString(pduDataStream, stringType);
	}

	/**
	 * Check TOKEN data defined in RFC2616.
	 * 
	 * @param ch
	 *            checking data
	 * @return true when ch is TOKEN, false when ch is not TOKEN
	 */
	protected static boolean isTokenCharacter(int ch) {
/**
         * Token      = 1*<any CHAR except CTLs or separators>
         * separators = "("(40) | ")"(41) | "<"(60) | ">"(62) | "@"(64)
         *            | ","(44) | ";"(59) | ":"(58) | "\"(92) | <">(34)
         *            | "/"(47) | "["(91) | "]"(93) | "?"(63) | "="(61)
         *            | "{"(123) | "}"(125) | SP(32) | HT(9)
         * CHAR       = <any US-ASCII character (octets 0 - 127)>
         * CTL        = <any US-ASCII control character
         *            (octets 0 - 31) and DEL (127)>
         * SP         = <US-ASCII SP, space (32)>
         * HT         = <US-ASCII HT, horizontal-tab (9)>
         */
		if ((ch < 33) || (ch > 126)) {
			return false;
		}

		switch (ch) {
		case '"': /* '"' */
		case '(': /* '(' */
		case ')': /* ')' */
		case ',': /* ',' */
		case '/': /* '/' */
		case ':': /* ':' */
		case ';': /* ';' */
		case '<': /* '<' */
		case '=': /* '=' */
		case '>': /* '>' */
		case '?': /* '?' */
		case '@': /* '@' */
		case '[': /* '[' */
		case '\\': /* '\' */
		case ']': /* ']' */
		case '{': /* '{' */
		case '}': /* '}' */
			return false;
		}

		return true;
	}

	/**
	 * Check TEXT data defined in RFC2616.
	 * 
	 * @param ch
	 *            checking data
	 * @return true when ch is TEXT, false when ch is not TEXT
	 */
	protected static boolean isText(int ch) {
		/**
		 * TEXT = <any OCTET except CTLs, but including LWS> CTL = <any US-ASCII
		 * control character (octets 0 - 31) and DEL (127)> LWS = [CRLF] 1*( SP
		 * | HT ) CRLF = CR LF CR = <US-ASCII CR, carriage return (13)> LF =
		 * <US-ASCII LF, linefeed (10)>
		 */
		if (((ch >= 32) && (ch <= 126)) || ((ch >= 128) && (ch <= 255))) {
			return true;
		}

		switch (ch) {
		case '\t': /* '\t' */
		case '\n': /* '\n' */
		case '\r': /* '\r' */
			return true;
		}

		return false;
	}

	protected static byte[] getWapString(ByteArrayInputStream pduDataStream, int stringType) {
		assert (null != pduDataStream);
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int temp = pduDataStream.read();
		assert (-1 != temp);
		while ((-1 != temp) && ('\0' != temp)) {
			// check each of the character
			if (stringType == TYPE_TOKEN_STRING) {
				if (isTokenCharacter(temp)) {
					out.write(temp);
				}
			} else {
				if (isText(temp)) {
					out.write(temp);
				}
			}

			temp = pduDataStream.read();
			assert (-1 != temp);
		}

		if (out.size() > 0) {
			return out.toByteArray();
		}

		return null;
	}

	/**
	 * Extract a byte value from the input stream.
	 *
	 * @param pduDataStream
	 *            pdu data input stream
	 * @return the byte
	 */
	protected static int extractByteValue(ByteArrayInputStream pduDataStream) {
		assert (null != pduDataStream);
		int temp = pduDataStream.read();
		assert (-1 != temp);
		return temp & 0xFF;
	}

	/**
	 * Parse Short-Integer.
	 *
	 * @param pduDataStream
	 *            pdu data input stream
	 * @return the byte
	 */
	protected static int parseShortInteger(ByteArrayInputStream pduDataStream) {
		/**
		 * From wap-230-wsp-20010705-a.pdf Short-integer = OCTET Integers in
		 * range 0-127 shall be encoded as a one octet value with the most
		 * significant bit set to one (1xxx xxxx) and with the value in the
		 * remaining least significant bits.
		 */
		assert (null != pduDataStream);
		int temp = pduDataStream.read();
		assert (-1 != temp);
		return temp & 0x7F;
	}

	/**
	 * Parse Long-Integer.
	 *
	 * @param pduDataStream
	 *            pdu data input stream
	 * @return long integer
	 */
	protected static long parseLongInteger(ByteArrayInputStream pduDataStream) {
		/**
		 * From wap-230-wsp-20010705-a.pdf Long-integer = Short-length
		 * Multi-octet-integer The Short-length indicates the length of the
		 * Multi-octet-integer Multi-octet-integer = 1*30 OCTET The content
		 * octets shall be an unsigned integer value with the most significant
		 * octet encoded first (big-endian representation). The minimum number
		 * of octets must be used to encode the value. Short-length = <Any octet
		 * 0-30>
		 */
		assert (null != pduDataStream);
		int temp = pduDataStream.read();
		assert (-1 != temp);
		int count = temp & 0xFF;

		if (count > LONG_INTEGER_LENGTH_MAX) {
			throw new RuntimeException("Octet count greater than 8 and I can't represent that!");
		}

		long result = 0;

		for (int i = 0; i < count; i++) {
			temp = pduDataStream.read();
			assert (-1 != temp);
			result <<= 8;
			result += (temp & 0xFF);
		}

		return result;
	}

	/**
	 * Parse Integer-Value.
	 *
	 * @param pduDataStream
	 *            pdu data input stream
	 * @return long integer
	 */
	protected static long parseIntegerValue(ByteArrayInputStream pduDataStream) {
		/**
		 * From wap-230-wsp-20010705-a.pdf Integer-Value = Short-integer |
		 * Long-integer
		 */
		assert (null != pduDataStream);
		pduDataStream.mark(1);
		int temp = pduDataStream.read();
		assert (-1 != temp);
		pduDataStream.reset();
		if (temp > SHORT_INTEGER_MAX) {
			return parseShortInteger(pduDataStream);
		} else {
			return parseLongInteger(pduDataStream);
		}
	}

	/**
	 * To skip length of the wap value.
	 *
	 * @param pduDataStream
	 *            pdu data input stream
	 * @param length
	 *            area size
	 * @return the values in this area
	 */
	protected static int skipWapValue(ByteArrayInputStream pduDataStream, int length) {
		assert (null != pduDataStream);
		byte[] area = new byte[length];
		int readLen = pduDataStream.read(area, 0, length);
		if (readLen < length) { // The actually read length is lower than the
								// length
			return -1;
		} else {
			return readLen;
		}
	}

	/**
	 * Parse content type parameters. For now we just support four parameters
	 * used in mms: "type", "start", "name", "charset".
	 *
	 * @param pduDataStream
	 *            pdu data input stream
	 * @param map
	 *            to store parameters of Content-Type field
	 * @param length
	 *            length of all the parameters
	 */
	protected static void parseContentTypeParams(ByteArrayInputStream pduDataStream, HashMap<Integer, Object> map, Integer length) {
		/**
		 * From wap-230-wsp-20010705-a.pdf Parameter = Typed-parameter |
		 * Untyped-parameter Typed-parameter = Well-known-parameter-token
		 * Typed-value the actual expected type of the value is implied by the
		 * well-known parameter Well-known-parameter-token = Integer-value the
		 * code values used for parameters are specified in the Assigned Numbers
		 * appendix Typed-value = Compact-value | Text-value In addition to the
		 * expected type, there may be no value. If the value cannot be encoded
		 * using the expected type, it shall be encoded as text. Compact-value =
		 * Integer-value | Date-value | Delta-seconds-value | Q-value |
		 * Version-value | Uri-value Untyped-parameter = Token-text
		 * Untyped-value the type of the value is unknown, but it shall be
		 * encoded as an integer, if that is possible. Untyped-value =
		 * Integer-value | Text-value
		 */
		assert (null != pduDataStream);
		assert (length > 0);

		int startPos = pduDataStream.available();
		int tempPos = 0;
		int lastLen = length;
		while (0 < lastLen) {
			int param = pduDataStream.read();
			assert (-1 != param);
			lastLen--;

			switch (param) {
			/**
			 * From rfc2387, chapter 3.1 The type parameter must be specified
			 * and its value is the MIME media type of the "root" body part. It
			 * permits a MIME user agent to determine the content-type without
			 * reference to the enclosed body part. If the value of the type
			 * parameter and the root body part's content-type differ then the
			 * User Agent's behavior is undefined.
			 *
			 * From wap-230-wsp-20010705-a.pdf type = Constrained-encoding
			 * Constrained-encoding = Extension-Media | Short-integer
			 * Extension-media = *TEXT End-of-string
			 */
			case PduPart.P_TYPE:
			case PduPart.P_CT_MR_TYPE:
				pduDataStream.mark(1);
				int first = extractByteValue(pduDataStream);
				pduDataStream.reset();
				if (first > TEXT_MAX) {
					// Short-integer (well-known type)
					int index = parseShortInteger(pduDataStream);

					if (index < PduContentTypes.contentTypes.length) {
						byte[] type = (PduContentTypes.contentTypes[index]).getBytes();
						map.put(PduPart.P_TYPE, type);
					} else {
						// not support this type, ignore it.
					}
				} else {
					// Text-String (extension-media)
					byte[] type = parseWapString(pduDataStream, TYPE_TEXT_STRING);
					if ((null != type) && (null != map)) {
						map.put(PduPart.P_TYPE, type);
					}
				}

				tempPos = pduDataStream.available();
				lastLen = length - (startPos - tempPos);
				break;

			/**
			 * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.3. Start Parameter
			 * Referring to Presentation
			 *
			 * From rfc2387, chapter 3.2 The start parameter, if given, is the
			 * content-ID of the compound object's "root". If not present the
			 * "root" is the first body part in the Multipart/Related entity.
			 * The "root" is the element the applications processes first.
			 *
			 * From wap-230-wsp-20010705-a.pdf start = Text-String
			 */
			case PduPart.P_START:
			case PduPart.P_DEP_START:
				byte[] start = parseWapString(pduDataStream, TYPE_TEXT_STRING);
				if ((null != start) && (null != map)) {
					map.put(PduPart.P_START, start);
				}

				tempPos = pduDataStream.available();
				lastLen = length - (startPos - tempPos);
				break;

			/**
			 * From oma-ts-mms-conf-v1_3.pdf In creation, the character set
			 * SHALL be either us-ascii (IANA MIBenum 3) or utf-8 (IANA MIBenum
			 * 106)[Unicode]. In retrieval, both us-ascii and utf-8 SHALL be
			 * supported.
			 *
			 * From wap-230-wsp-20010705-a.pdf charset =
			 * Well-known-charset|Text-String Well-known-charset = Any-charset |
			 * Integer-value Both are encoded using values from Character Set
			 * Assignments table in Assigned Numbers Any-charset = <Octet 128>
			 * Equivalent to the special RFC2616 charset value "*"
			 */
			case PduPart.P_CHARSET:
				pduDataStream.mark(1);
				int firstValue = extractByteValue(pduDataStream);
				pduDataStream.reset();
				// Check first char
				if (((firstValue > TEXT_MIN) && (firstValue < TEXT_MAX)) || (END_STRING_FLAG == firstValue)) {
					// Text-String (extension-charset)
					byte[] charsetStr = parseWapString(pduDataStream, TYPE_TEXT_STRING);
					try {
						int charsetInt = CharacterSets.getMibEnumValue(new String(charsetStr));
						map.put(PduPart.P_CHARSET, charsetInt);
					} catch (UnsupportedEncodingException e) {
						// Not a well-known charset, use "*".
						map.put(PduPart.P_CHARSET, CharacterSets.ANY_CHARSET);
					}
				} else {
					// Well-known-charset
					int charset = (int) parseIntegerValue(pduDataStream);
					if (map != null) {
						map.put(PduPart.P_CHARSET, charset);
					}
				}

				tempPos = pduDataStream.available();
				lastLen = length - (startPos - tempPos);
				break;

			/**
			 * From oma-ts-mms-conf-v1_3.pdf A name for multipart object SHALL
			 * be encoded using name-parameter for Content-Type header in WSP
			 * multipart headers.
			 *
			 * From wap-230-wsp-20010705-a.pdf name = Text-String
			 */
			case PduPart.P_DEP_NAME:
			case PduPart.P_NAME:
				byte[] name = parseWapString(pduDataStream, TYPE_TEXT_STRING);
				if ((null != name) && (null != map)) {
					map.put(PduPart.P_NAME, name);
				}

				tempPos = pduDataStream.available();
				lastLen = length - (startPos - tempPos);
				break;
			default:
				if (-1 == skipWapValue(pduDataStream, lastLen)) {
				} else {
					lastLen = 0;
				}
				break;
			}
		}

		if (0 != lastLen) {
		}
	}

	/**
	 * Parse content type.
	 *
	 * @param pduDataStream
	 *            pdu data input stream
	 * @param map
	 *            to store parameters in Content-Type header field
	 * @return Content-Type value
	 */
	protected static byte[] parseContentType(ByteArrayInputStream pduDataStream, HashMap<Integer, Object> map) {
		/**
		 * From wap-230-wsp-20010705-a.pdf Content-type-value =
		 * Constrained-media | Content-general-form Content-general-form =
		 * Value-length Media-type Media-type = (Well-known-media |
		 * Extension-Media) *(Parameter)
		 */
		assert (null != pduDataStream);

		byte[] contentType = null;
		pduDataStream.mark(1);
		int temp = pduDataStream.read();
		assert (-1 != temp);
		pduDataStream.reset();

		int cur = (temp & 0xFF);

		if (cur < TEXT_MIN) {
			int length = parseValueLength(pduDataStream);
			int startPos = pduDataStream.available();
			pduDataStream.mark(1);
			temp = pduDataStream.read();
			assert (-1 != temp);
			pduDataStream.reset();
			int first = (temp & 0xFF);

			if ((first >= TEXT_MIN) && (first <= TEXT_MAX)) {
				contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
			} else if (first > TEXT_MAX) {
				int index = parseShortInteger(pduDataStream);

				if (index < PduContentTypes.contentTypes.length) { // well-known
																	// type
					contentType = (PduContentTypes.contentTypes[index]).getBytes();
				} else {
					pduDataStream.reset();
					contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
				}
			} else {
				return (PduContentTypes.contentTypes[0]).getBytes(); // "*/*"
			}

			int endPos = pduDataStream.available();
			int parameterLen = length - (startPos - endPos);
			if (parameterLen > 0) {// have parameters
				parseContentTypeParams(pduDataStream, map, parameterLen);
			}

			if (parameterLen < 0) {
				return (PduContentTypes.contentTypes[0]).getBytes(); // "*/*"
			}
		} else if (cur <= TEXT_MAX) {
			contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
		} else {
			contentType = (PduContentTypes.contentTypes[parseShortInteger(pduDataStream)]).getBytes();
		}

		return contentType;
	}

	/**
	 * Parse part's headers.
	 *
	 * @param pduDataStream
	 *            pdu data input stream
	 * @param part
	 *            to store the header informations of the part
	 * @param length
	 *            length of the headers
	 * @return true if parse successfully, false otherwise
	 */
	protected static boolean parsePartHeaders(ByteArrayInputStream pduDataStream, PduPart part, int length) {
		assert (null != pduDataStream);
		assert (null != part);
		assert (length > 0);

		/**
		 * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2. A name for multipart
		 * object SHALL be encoded using name-parameter for Content-Type header
		 * in WSP multipart headers. In decoding, name-parameter of Content-Type
		 * SHALL be used if available. If name-parameter of Content-Type is not
		 * available, filename parameter of Content-Disposition header SHALL be
		 * used if available. If neither name-parameter of Content-Type header
		 * nor filename parameter of Content-Disposition header is available,
		 * Content-Location header SHALL be used if available.
		 *
		 * Within SMIL part the reference to the media object parts SHALL use
		 * either Content-ID or Content-Location mechanism [RFC2557] and the
		 * corresponding WSP part headers in media object parts contain the
		 * corresponding definitions.
		 */
		int startPos = pduDataStream.available();
		int tempPos = 0;
		int lastLen = length;
		while (0 < lastLen) {
			int header = pduDataStream.read();
			assert (-1 != header);
			lastLen--;

			if (header > TEXT_MAX) {
				// Number assigned headers.
				switch (header) {
				case PduPart.P_CONTENT_LOCATION:
					/**
					 * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
					 * Content-location-value = Uri-value
					 */
					byte[] contentLocation = parseWapString(pduDataStream, TYPE_TEXT_STRING);
					if (null != contentLocation) {
						part.setContentLocation(contentLocation);
					}

					tempPos = pduDataStream.available();
					lastLen = length - (startPos - tempPos);
					break;
				case PduPart.P_CONTENT_ID:
					/**
					 * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
					 * Content-ID-value = Quoted-string
					 */
					byte[] contentId = parseWapString(pduDataStream, TYPE_QUOTED_STRING);
					if (null != contentId) {
						part.setContentId(contentId);
					}

					tempPos = pduDataStream.available();
					lastLen = length - (startPos - tempPos);
					break;
				case PduPart.P_DEP_CONTENT_DISPOSITION:
				case PduPart.P_CONTENT_DISPOSITION:
					/**
					 * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
					 * Content-disposition-value = Value-length Disposition
					 * *(Parameter) Disposition = Form-data | Attachment |
					 * Inline | Token-text Form-data = <Octet 128> Attachment =
					 * <Octet 129> Inline = <Octet 130>
					 */

					/*
					 * some carrier mmsc servers do not support
					 * content_disposition field correctly
					 */
					boolean contentDisposition = true;

					if (contentDisposition) {
						int len = -1;
						boolean validDispositionLength = true;
						pduDataStream.mark(1);

						try {
							len = parseValueLength(pduDataStream);
						} catch (RuntimeException e) {
							// tolerate invalid content-disposition length
							len = 31;
							validDispositionLength = false;
							pduDataStream.reset();
						}

						pduDataStream.mark(1);
						int thisStartPos = pduDataStream.available();
						int thisEndPos = 0;
						int value = pduDataStream.read();

						if (validDispositionLength) {
							if (value == PduPart.P_DISPOSITION_FROM_DATA) {
								part.setContentDisposition(PduPart.DISPOSITION_FROM_DATA);
							} else if (value == PduPart.P_DISPOSITION_ATTACHMENT) {
								part.setContentDisposition(PduPart.DISPOSITION_ATTACHMENT);
							} else if (value == PduPart.P_DISPOSITION_INLINE) {
								part.setContentDisposition(PduPart.DISPOSITION_INLINE);
							} else {
								pduDataStream.reset();
								/* Token-text */
								part.setContentDisposition(parseWapString(pduDataStream, TYPE_TEXT_STRING));
							}
						} else {
							pduDataStream.reset();
							/* Token-text */
							part.setContentDisposition(parseWapString(pduDataStream, TYPE_TEXT_STRING));
						}

						/* get filename parameter and skip other parameters */
						thisEndPos = pduDataStream.available();
						if (thisStartPos - thisEndPos < len) {
							value = pduDataStream.read();
							if (value == PduPart.P_FILENAME) { // filename is
																// text-string
								part.setFilename(parseWapString(pduDataStream, TYPE_TEXT_STRING));
							}

							/* skip other parameters */
							thisEndPos = pduDataStream.available();
							if (thisStartPos - thisEndPos < len) {
								int last = len - (thisStartPos - thisEndPos);
								byte[] temp = new byte[last];
								pduDataStream.read(temp, 0, last);
							}
						}

						tempPos = pduDataStream.available();
						lastLen = length - (startPos - tempPos);
					}
					break;
				default:
					if (-1 == skipWapValue(pduDataStream, lastLen)) {
						return false;
					}
					lastLen = 0;
					break;
				}
			} else if ((header >= TEXT_MIN) && (header <= TEXT_MAX)) {
				// Not assigned header.
				byte[] tempHeader = parseWapString(pduDataStream, TYPE_TEXT_STRING);
				byte[] tempValue = parseWapString(pduDataStream, TYPE_TEXT_STRING);

				// Check the header whether it is "Content-Transfer-Encoding".
				if (true == PduPart.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(new String(tempHeader))) {
					part.setContentTransferEncoding(tempValue);
				}

				tempPos = pduDataStream.available();
				lastLen = length - (startPos - tempPos);
			} else {
				// Skip all headers of this part.
				if (-1 == skipWapValue(pduDataStream, lastLen)) {
					return false;
				}
				lastLen = 0;
			}
		}

		if (0 != lastLen) {
			return false;
		}

		return true;
	}

	/**
	 * Check the position of a specified part.
	 *
	 * @param part
	 *            the part to be checked
	 * @return part position, THE_FIRST_PART when it's the first one,
	 *         THE_LAST_PART when it's the last one.
	 */
	private static int checkPartPosition(PduPart part) {
		assert (null != part);
		if ((null == mTypeParam) && (null == mStartParam)) {
			return THE_LAST_PART;
		}

		/* check part's content-id */
		if (null != mStartParam) {
			byte[] contentId = part.getContentId();
			if (null != contentId) {
				if (true == Arrays.equals(mStartParam, contentId)) {
					return THE_FIRST_PART;
				}
			}
		}

		/* check part's content-type */
		if (null != mTypeParam) {
			byte[] contentType = part.getContentType();
			if (null != contentType) {
				if (true == Arrays.equals(mTypeParam, contentType)) {
					return THE_FIRST_PART;
				}
			}
		}

		return THE_LAST_PART;
	}

	/**
	 * Check mandatory headers of a pdu.
	 *
	 * @param headers
	 *            pdu headers
	 * @return true if the pdu has all of the mandatory headers, false
	 *         otherwise.
	 */
	protected static boolean checkMandatoryHeader(PduHeaders headers) {
		if (null == headers) {
			return false;
		}

		/* get message type */
		int messageType = headers.getOctet(PduHeaders.MESSAGE_TYPE);

		/* check Mms-Version field */
		int mmsVersion = headers.getOctet(PduHeaders.MMS_VERSION);
		if (0 == mmsVersion) {
			// Every message should have Mms-Version field.
			return false;
		}

		/* check mandatory header fields */
		switch (messageType) {
		case PduHeaders.MESSAGE_TYPE_SEND_REQ:
			// Content-Type field.
			byte[] srContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
			if (null == srContentType) {
				return false;
			}

			// From field.
			EncodedStringValue srFrom = headers.getEncodedStringValue(PduHeaders.FROM);
			if (null == srFrom) {
				return false;
			}

			// Transaction-Id field.
			byte[] srTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
			if (null == srTransactionId) {
				return false;
			}

			break;
		case PduHeaders.MESSAGE_TYPE_SEND_CONF:
			// Response-Status field.
			int scResponseStatus = headers.getOctet(PduHeaders.RESPONSE_STATUS);
			if (0 == scResponseStatus) {
				return false;
			}

			// Transaction-Id field.
			byte[] scTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
			if (null == scTransactionId) {
				return false;
			}

			break;
		case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
			// Content-Location field.
			byte[] niContentLocation = headers.getTextString(PduHeaders.CONTENT_LOCATION);
			if (null == niContentLocation) {
				return false;
			}

			// Expiry field.
			long niExpiry = headers.getLongInteger(PduHeaders.EXPIRY);
			if (-1 == niExpiry) {
				return false;
			}

			// Message-Class field.
			byte[] niMessageClass = headers.getTextString(PduHeaders.MESSAGE_CLASS);
			if (null == niMessageClass) {
				return false;
			}

			// Message-Size field.
			long niMessageSize = headers.getLongInteger(PduHeaders.MESSAGE_SIZE);
			if (-1 == niMessageSize) {
				return false;
			}

			// Transaction-Id field.
			byte[] niTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
			if (null == niTransactionId) {
				return false;
			}

			break;
		case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
			// Status field.
			int nriStatus = headers.getOctet(PduHeaders.STATUS);
			if (0 == nriStatus) {
				return false;
			}

			// Transaction-Id field.
			byte[] nriTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
			if (null == nriTransactionId) {
				return false;
			}

			break;
		case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
			// Content-Type field.
			byte[] rcContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
			if (null == rcContentType) {
				return false;
			}

			// Date field.
			long rcDate = headers.getLongInteger(PduHeaders.DATE);
			if (-1 == rcDate) {
				return false;
			}

			break;
		case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
			// Date field.
			long diDate = headers.getLongInteger(PduHeaders.DATE);
			if (-1 == diDate) {
				return false;
			}

			// Message-Id field.
			byte[] diMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
			if (null == diMessageId) {
				return false;
			}

			// Status field.
			int diStatus = headers.getOctet(PduHeaders.STATUS);
			if (0 == diStatus) {
				return false;
			}

			// To field.
			EncodedStringValue[] diTo = headers.getEncodedStringValues(PduHeaders.TO);
			if (null == diTo) {
				return false;
			}

			break;
		case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
			// Transaction-Id field.
			byte[] aiTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
			if (null == aiTransactionId) {
				return false;
			}

			break;
		case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
			// Date field.
			long roDate = headers.getLongInteger(PduHeaders.DATE);
			if (-1 == roDate) {
				return false;
			}

			// From field.
			EncodedStringValue roFrom = headers.getEncodedStringValue(PduHeaders.FROM);
			if (null == roFrom) {
				return false;
			}

			// Message-Id field.
			byte[] roMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
			if (null == roMessageId) {
				return false;
			}

			// Read-Status field.
			int roReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
			if (0 == roReadStatus) {
				return false;
			}

			// To field.
			EncodedStringValue[] roTo = headers.getEncodedStringValues(PduHeaders.TO);
			if (null == roTo) {
				return false;
			}

			break;
		case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
			// From field.
			EncodedStringValue rrFrom = headers.getEncodedStringValue(PduHeaders.FROM);
			if (null == rrFrom) {
				return false;
			}

			// Message-Id field.
			byte[] rrMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
			if (null == rrMessageId) {
				return false;
			}

			// Read-Status field.
			int rrReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
			if (0 == rrReadStatus) {
				return false;
			}

			// To field.
			EncodedStringValue[] rrTo = headers.getEncodedStringValues(PduHeaders.TO);
			if (null == rrTo) {
				return false;
			}

			break;
		default:
			// Parser doesn't support this message type in this version.
			return false;
		}

		return true;
	}
}
